package cn.edu.dgut.css.sai.course.wechatoauth2demo.config;

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.util.Assert;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.Map;
import java.util.Set;

/**
 * @author sai
 * @since 2020/12/26
 */
public final class WeChatOAuth2AccessTokenResponseClient implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {

    private final RestTemplateBuilder restTemplateBuilder;
    private final WeChatOAuth2ClientProperties weChatOAuth2ClientProperties;

    public WeChatOAuth2AccessTokenResponseClient(RestTemplateBuilder restTemplateBuilder, WeChatOAuth2ClientProperties weChatOAuth2ClientProperties) {
        this.restTemplateBuilder = restTemplateBuilder;
        this.weChatOAuth2ClientProperties = weChatOAuth2ClientProperties;
    }

    /**
     * <p>自定义{@link OAuth2AccessTokenResponseClient} 用于获取access_token</p>
     * <p>参考文档：https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html</p>
     */
    @Override
    public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) {
        // @formatter:off
        UriComponents uriComponents = UriComponentsBuilder.fromUriString(weChatOAuth2ClientProperties.getAccessTokenUrl())
                                                            .queryParam("appid", weChatOAuth2ClientProperties.getAppId())
                                                            .queryParam("secret", weChatOAuth2ClientProperties.getAppSecret())
                                                            .queryParam("code", authorizationGrantRequest.getAuthorizationExchange().getAuthorizationResponse().getCode())
                                                            .queryParam("grant_type","authorization_code")
                                                            .encode()
                                                            .build();
        // 构造请求正文
        RequestEntity<Void> accessTokenRequestEntity = RequestEntity.get(uriComponents.toUri()).build();
        // 请求access_token
        ResponseEntity<Map<String, Object>> accessTokenResponseEntity = restTemplateBuilder.build().exchange(accessTokenRequestEntity, new ParameterizedTypeReference<>() {});
        // @formatter:on

        // 转换为 OAuth2AccessTokenResponse
        Map<String, Object> accessTokenResponsBody = accessTokenResponseEntity.getBody();
        Assert.notNull(accessTokenResponsBody, "accessTokenResponsBody 不能为 null");

        // @formatter:off
        return OAuth2AccessTokenResponse
                    .withToken(accessTokenResponsBody.get("access_token").toString())
                    .tokenType(OAuth2AccessToken.TokenType.BEARER) // Spring Security OAuth2 内部不允许 TokenType 为 空。
                    .refreshToken(accessTokenResponsBody.get("refresh_token").toString())
                    .expiresIn(Integer.parseInt(accessTokenResponsBody.get("expires_in").toString()))
                    .scopes(Set.of("WeChat", weChatOAuth2ClientProperties.getScope())) // 自定义一个
                    // 微信网页授权的accessToken接口的返回中包含了openid和unionid，它是用于获取用户信息的，所以把它设置在额外参数属性中，以后会用到。
                    .additionalParameters(Map.of("openid", accessTokenResponsBody.get("openid"), "unionid", accessTokenResponsBody.get("unionid")))
                .build();
        // @formatter:on
    }
}
